package db

import (
	"fmt"
	"github.com/beego/beego/v2/core/logs"
	"go1/db/cb"
	"log"
	"reflect"
	"strings"
)

func BuildFilter[T any](object string, filter1 any, opts *CrudOpts) *FilterParams {
	filterArgs := InitFilterArgs[T](object)
	filterArgs.Params = opts.Params
	iteratorFilter("", object, filter1, filterArgs, opts)
	s1 := joinFilter(filterArgs, opts)
	filterArgs.Joiner = " OR "
	depthFilter := map[int]map[string]DepthObjF{}
	filterArgs.DepthFilter = depthFilter
	iteratorFilter("", object, filterArgs.OrFilter, filterArgs, opts)
	s2 := joinFilter(filterArgs, opts)
	sql := ""
	if len(s2) > 0 {
		if len(s1) == 0 {
			sql = "WHERE " + s2
		}
		sql = "WHERE " + s1 + " AND (" + s2 + ")"
	} else {
		if len(s1) > 0 {
			sql = "WHERE " + s1
		}
		sql = s1
	}
	filterArgs.Sql = sql
	return filterArgs
}

func iteratorFilter(parent string, object string, filter1 any, filterArgs *FilterParams, opts *CrudOpts) {
	if filter1 == nil {
		return
	}
	if reflect.TypeOf(filter1) == reflect.TypeOf([]map[string]any{}) {
		filter := filter1.([]map[string]any)
		for _, mF := range filter {
			iteratorFilter(parent, object, mF, filterArgs, opts)
		}
		return
	}
	if reflect.TypeOf(filter1) == reflect.TypeOf(map[string]any{}) {
		filter := filter1.(map[string]any)
		for key, value := range filter {
			parseFilter(parent, object, key, value, filterArgs, opts)
		}
		return
	}
	if reflect.TypeOf(filter1) != reflect.TypeOf([]any{}) {
		logs.Debug("filter1 must be a map or a list or [map], %v", filter1)
		panic("filter_type_error")
	}
	filter := filter1.([]any)
	if len(filter) == 0 {
		return
	}
	for _, fi1 := range filter {
		if reflect.TypeOf(fi1) != reflect.TypeOf([]any{}) {
			logs.Debug("filter type error %v, type %v", filter, reflect.TypeOf(fi1))
			panic("filter_type_error")
		}
		fi := fi1.([]any)
		fiLen := len(fi)
		if fiLen != 2 {
			panic("filter_len_error")
		}
		key := fi[0]
		if reflect.TypeOf(key) != reflect.TypeOf("") {
			panic("filter_type_error")
		}
		parseFilter(parent, object, key.(string), fi[1], filterArgs, opts)
	}
}

func parseFilter(parent string, object string, key string, value any, filterArgs *FilterParams, opts *CrudOpts) {
	if key == "OR" || key == "or" {
		vType := reflect.TypeOf(value)
		if vType != reflect.TypeOf([]map[string]any{}) &&
			vType != reflect.TypeOf([]any{}) &&
			vType != reflect.TypeOf(map[string]any{}) {
			logs.Debug("or filter_type_error, value: %v, vType: %s", value, vType)
			panic("filter_type_error")
		}
		if filterArgs.OrFilter != nil {
			logs.Debug("filter_or_len_error, %s, %s, %s, %s", parent, object, key, value)
			panic("filter_or_appeared")
		}
		filterArgs.OrFilter = value
		return
	}
	k11 := strings.Split(key, ",")
	lK11 := len(k11)
	if lK11 > 2 {
		logs.Error("filter_len_error, %v", key)
		panic("filter_op_error")
	}
	czf := "="
	if lK11 == 2 {
		czf = k11[1]
	}
	k111 := strings.Split(k11[0], ".")
	lK111 := len(k111)
	if lK111 > 1 {
		foreObj := k111[0]
		field := getField(foreObj, filterArgs)
		logs.Debug("check fore field type", field, field.Type.Elem().Kind())
		if field.Type != nil && field.Type.Elem().Kind() == reflect.Struct {
			addDepth(filterArgs, 1)
			old := filterArgs.Method
			method, b := field.Type.MethodByName("GetListFilter")
			newM := DeeprMethod{
				EntType: field.Type,
				Method:  method,
				Has:     b,
				Entity:  reflect.New(field.Type.Elem()),
			}
			filterArgs.Method = newM
			parseFilter(object, foreObj, k11[0][len(foreObj)+1:], value, filterArgs, opts)
			addDepth(filterArgs, -1)
			filterArgs.Method = old
			return
		}
	}
	appendToObjFilter(filterArgs, object, parent, k11[0], value, czf, opts)
}

func BuildSelect(obj string, fields []string, filterPrams *FilterParams, opts *CrudOpts) string {
	if len(fields) == 0 {
		panic("select_is_empty")
	}
	//op := pageRequest.Optional
	//ma := pageRequest.Match
	s := make(map[string][]string)
	s[obj] = fields
	sql := BuildOM(obj, s, filterPrams, opts)
	//if len(ma) > 0 {
	//	sql += "," + c.BuildOM(obj, ma, filterPrams, opts)
	//}
	//if len(op) > 0 {
	//	sql += "," + c.BuildOM(obj, op, filterPrams, opts)
	//}
	return sql
}

func BuildOM(obj string, op map[string][]string, filterPrams *FilterParams, opts *CrudOpts) string {
	sql := ""
	if len(op) == 0 {
		return sql
	}
	t := filterPrams.TType
	log.Println("t-----", t)
	var method reflect.Method
	var b bool
	jgt := reflect.ValueOf(filterPrams.T)
	foreign := filterPrams.Foreign
	for opObj, fields := range op {
		if opObj == obj {
			method, b = t.MethodByName("GetListSelect")
		} else {
			oF, oFb := t.FieldByName(strings.Title(opObj))
			if !oFb {
				panic(fmt.Sprintf("%s_not_found", opObj))
			}
			foreign = append(foreign, oF)
			t = oF.Type.Elem()
			method, b = t.MethodByName("GetListSelect")
			jgt = reflect.New(t).Elem()
		}
		if sql != "" {
			sql += ","
		}
		opStr := ""
		for index, field := range fields {
			if index > 0 {
				opStr += ","
			}
			field1 := strings.Title(field)
			_, err := t.FieldByName(field1)
			if !err {
				panic(fmt.Sprintf("%s_not_found", field))
			}
			if b {
				a := opts.Args
				mA := []reflect.Value{jgt, reflect.ValueOf(field), reflect.ValueOf(a)}
				r := method.Func.Call(mA)[0].String()
				//opStr += opObj + "." + r
				opStr += r
				continue
			}
			//opStr += opObj + "." + field
			opStr += field
		}
		sql += opStr
	}
	filterPrams.Foreign = foreign
	return sql
}

func GetForeignTableName(field reflect.Type, opts *CrudOpts) string {
	method, b := field.MethodByName("GetTableName")
	if !b {
		panic(fmt.Errorf("%v not found method GetTableName", field))
	}
	mA := []reflect.Value{reflect.New(field.Elem()), reflect.ValueOf(opts.Args), reflect.ValueOf(opts)}
	return method.Func.Call(mA)[0].String()
}

func SetToRow(m map[string]any, ty reflect.Type, nv reflect.Value) {
	for i := 0; i < ty.NumField(); i++ {
		field := ty.Field(i)
		fieldName := GetJsonTagIndex(field, 0)
		fieldValue := nv.FieldByName(field.Name)
		if value, ok := m[fieldName]; ok {
			switch fieldValue.Kind() {
			case reflect.String:
				fieldValue.SetString(value.(string))
			case reflect.Float64, reflect.Float32:
				fieldValue.SetFloat(value.(float64))
			case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
				switch reflect.TypeOf(value).Kind() {
				case reflect.Float64, reflect.Float32:
					v1 := int64(value.(float64))
					fieldValue.SetInt(v1)
				case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
					fieldValue.SetInt(value.(int64))
				default:
					logs.Error("type error %s, %v, %v", fieldName, fieldValue.Kind(), reflect.TypeOf(value).Kind())
				}
			case reflect.Bool:
				fieldValue.SetBool(value.(bool))
			case reflect.Array:
				a := value.([]any)
				fieldValue.Set(reflect.ValueOf(a))
			case reflect.Map:
				a := value.(map[string]any)
				fieldValue.Set(reflect.ValueOf(a))
			// 根据需要添加其他类型的case
			default:
				logs.Error("%v, fieldName: %s, unsupported field type: %v", ty, fieldName, value)
			}
		}
	}
}

func GetTagIndex(field reflect.StructField, tagName string, index int) string {
	tag := field.Tag.Get(tagName)
	if tag == "" {
		tag = field.Name
	}
	return strings.Split(tag, ",")[index]
}

func GetJsonTagIndex(field reflect.StructField, index int) string {
	return GetTagIndex(field, "json", index)
}

func GetForeTempObj(name string) string {
	return "temp_" + name
}

func appendToObjFilter(params *FilterParams, obj string, parent string, key string, v any, czf string, opts *CrudOpts) {
	method := params.Method
	if method.Has {
		m := method.Method
		t := method.Entity
		if method.EntType == nil {
			t = reflect.New(method.EntType)
			method.Entity = t
		}
		params.Method = method
		mA := []reflect.Value{t, reflect.ValueOf(params),
			reflect.ValueOf(obj), reflect.ValueOf(parent),
			reflect.ValueOf(key), reflect.ValueOf(v), reflect.ValueOf(czf), reflect.ValueOf(opts)}
		m.Func.Call(mA)
		return
	}
	AppendToFilter(params, obj, parent, key, v, czf, opts)
}

func AppendToFilter(params *FilterParams, obj string, parent string, key string, v any, czf string, opts *CrudOpts) {
	depth := params.Depth
	depthFilters := params.DepthFilter
	depthF := depthFilters[depth]

	if depthF == nil {
		depthF = make(map[string]DepthObjF)
		depthFilters[depth] = depthF
	}
	depthObjF := depthF[obj]
	curSql := depthObjF.Sql
	if len(curSql) > 0 {
		curSql += params.Joiner
	}
	sql := ""
	index := params.Index
	deepMe := params.Method
	switch reflect.TypeOf(v) {
	case reflect.TypeOf([]any{}), reflect.TypeOf([]int{}), reflect.TypeOf([]float64{}), reflect.TypeOf([]float32{}):
		kF, _ := deepMe.EntType.Elem().FieldByName(strings.Title(key))
		logs.Debug("AppendToFilter, keyType:", kF.Type, ", key:", key, ", foreField:", deepMe.EntType)
		switch kF.Type {
		case reflect.TypeOf([]any{}), reflect.TypeOf([]int{}), reflect.TypeOf([]float64{}), reflect.TypeOf([]float32{}):
			sql = fmt.Sprintf("ANY %s IN %s SATISFIES %s IN $%d END", key+"_s", key, key+"_s", index)
		default:
			sql = fmt.Sprintf("%s IN $%d", key, index)
		}
	default:
		logs.Debug("AppendToFilter field: %s, valType: %s", key, reflect.TypeOf(v))
		sql = fmt.Sprintf("%s %s $%d", key, czf, index)
	}
	curSql += sql
	params.Params = append(params.Params, v)
	params.Index = index + 1
	depthObjF.Sql = curSql
	depthObjF.Parent = parent
	depthF[obj] = depthObjF
	params.DepthFilter[depth] = depthF
}

func joinFilter(params *FilterParams, opts *CrudOpts) string {
	depthFilter := params.DepthFilter
	if len(depthFilter) == 0 {
		return ""
	}
	//var t T
	mm := make(map[string]reflect.Type)
	mm[fmt.Sprintf("%d-%s", 1, params.TargetObj)] = params.TType
	for i := 1; i <= len(depthFilter)+1; i++ {
		curDObjFil := depthFilter[i]
		for obj1, dObjF := range curDObjFil {
			if i == 1 {
				continue
			}
			parentField := mm[fmt.Sprintf("%d-%s", i-1, dObjF.Parent)]
			obj := strings.Title(obj1)
			rfField, _ := parentField.FieldByName(obj)
			a := rfField.Type.Elem()
			mm[fmt.Sprintf("%d-%s", i, obj1)] = a
			foreKey := GetTagIndex(rfField, "foreignKey", 1)
			logs.Debug("parent: %v, foreObj: %s, foreKey: %s", parentField, obj1, foreKey)
			mA := []reflect.Value{reflect.New(a), reflect.ValueOf(opts.Args), reflect.ValueOf(opts)}
			method, b := rfField.Type.MethodByName("GetTableName")
			if !b {
				panic(fmt.Errorf("%s not have GetTableName method, %v", obj, a))
			}
			col := method.Func.Call(mA)[0].String()
			opts.Collection = col
			tableName := cb.GetTableName(opts)
			sql := foreKey + " IN (SELECT RAW META().id FROM " + tableName + " WHERE " + dObjF.Sql + ")"
			last := depthFilter[i-1]
			if last == nil {
				last = make(map[string]DepthObjF)
			}
			lastOS := last[dObjF.Parent]
			if lastOS.Sql != "" {
				lastOS.Sql += params.Joiner + sql
			} else {
				lastOS.Sql = sql
			}
			last[dObjF.Parent] = lastOS
			depthFilter[i-1] = last
		}
	}
	sql := ""
	l := len(depthFilter)
	for i := 1; i <= l; i++ {
		curDObjFil := depthFilter[i]
		for obj, dObjF := range curDObjFil {
			logs.Debug("depth: %d, obj: %s, curDObjFil: %v", i, obj, dObjF)
			if i < l || obj == params.TargetObj {
				if sql != "" {
					sql += " AND " + dObjF.Sql
				} else {
					sql += dObjF.Sql
				}
			}
		}
	}
	return sql
}

func InitFilterArgs[T any](obj string) *FilterParams {
	var d T
	t := reflect.TypeOf(d)
	method, b := t.MethodByName("GetListFilter")
	m := DeeprMethod{
		Method:  method,
		Entity:  reflect.ValueOf(d),
		EntType: t,
		Has:     b,
	}
	return &FilterParams{Depth: 1, TargetObj: obj, Joiner: " AND ",
		Params: []any{}, DepthFilter: map[int]map[string]DepthObjF{},
		Index: 1, Method: m, T: d, TType: t}
}

func addDepth(params *FilterParams, v int) {
	params.Depth = params.Depth + v
}

func GetForeKey(tag []string) string {
	if len(tag) > 1 {
		return tag[1]
	}
	return tag[0]
}

func getField(field string, filterArgs *FilterParams) reflect.StructField {
	a, b := filterArgs.TType.FieldByName(strings.Title(field))
	if !b {
		return reflect.StructField{Type: nil}
	}
	return a
}
